﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using GingerBeard.Utilities;
using System.IO;
using System.Reflection;
using System.Diagnostics;

namespace GingerBeard.ColladaHelpers
{
	public class ColladaReader
	{
		public static ColladaFile Create(String filename)
		{
			using (FileStream s = new FileStream(filename, FileMode.Open, FileAccess.Read))
			{
				return Create(s);
			}
		}

		public static ColladaFile Create(Stream stream)
		{
			using (StreamReader r = new StreamReader(stream))
			{
				return Create(r);
			}
		}

		public static ColladaFile Create(TextReader reader)
		{
			using (XmlReader r = XmlReader.Create(reader))
			{
				return XmlParser.LoadNode(r, null, nodeRecords) as ColladaFile;
			}
		}

		#region Node Processing

		private static XmlParser.NodeRecord[] nodeRecords = 
		{
			new XmlParser.NodeRecord("collada", ProcessGenericNode<ColladaFile>),
			new XmlParser.NodeRecord("asset", ProcessAsset),
			new XmlParser.NodeRecord("contributor", ProcessContributor),
			new XmlParser.NodeRecord("unit", ProcessUnit),
			new XmlParser.NodeRecord("library_cameras", ProcessGenericNode<ColladaLibrary<ColladaCamera>>),
			new XmlParser.NodeRecord("library_lights", ProcessGenericNode<ColladaLibrary<ColladaLight>>),
			new XmlParser.NodeRecord("library_materials", ProcessGenericNode<ColladaLibrary<ColladaMaterial>>),
			new XmlParser.NodeRecord("library_effects", ProcessGenericNode<ColladaLibrary<ColladaEffect>>),
			new XmlParser.NodeRecord("library_geometries", ProcessGenericNode<ColladaLibrary<ColladaGeometry>>),
			new XmlParser.NodeRecord("library_visual_scenes", ProcessGenericNode<ColladaLibrary<ColladaVisualScene>>),
			new XmlParser.NodeRecord("camera", ProcessGenericNode<ColladaCamera>),
			new XmlParser.NodeRecord("optics", ProcessGenericNode<ColladaOptics>),
			new XmlParser.NodeRecord("perspective", ProcessPerspective),
			new XmlParser.NodeRecord("light", ProcessGenericNode<ColladaLight>),
			new XmlParser.NodeRecord("point", ProcessPointLight),
			new XmlParser.NodeRecord("material", ProcessGenericNode<ColladaMaterial>),
			new XmlParser.NodeRecord("instance_effect", ProcessInstance<ColladaEffect>),
			new XmlParser.NodeRecord("effect", ProcessGenericNode<ColladaEffect>),
			new XmlParser.NodeRecord("technique", ProcessTechnique),
			new XmlParser.NodeRecord("technique_common", ProcessGenericNode<ColladaTechniqueCommon>),
			new XmlParser.NodeRecord("phong", ProcessPhong),
			new XmlParser.NodeRecord("geometry", ProcessGenericNode<ColladaGeometry>),
			new XmlParser.NodeRecord("mesh", ProcessGenericNode<ColladaMesh>),
			new XmlParser.NodeRecord("source", ProcessGenericNode<ColladaSource>),
			new XmlParser.NodeRecord("float_array", ProcessArray<float>),
			new XmlParser.NodeRecord("accessor", ProcessAccessor),
			new XmlParser.NodeRecord("param", ProcessParam),
			new XmlParser.NodeRecord("vertices", ProcessGenericNode<ColladaVertices>),
			new XmlParser.NodeRecord("input", ProcessInput),
			new XmlParser.NodeRecord("polylist", ProcessPolylist),
			new XmlParser.NodeRecord("visual_scene", ProcessGenericNode<ColladaVisualScene>),
			new XmlParser.NodeRecord("node", ProcessVisualSceneNode),
			new XmlParser.NodeRecord("scene", ProcessGenericNode<ColladaScene>),
			new XmlParser.NodeRecord("instance_visual_scene", ProcessInstance<ColladaVisualScene>),
			new XmlParser.NodeRecord("bind_material", ProcessInstance<ColladaBindMaterial>),
			new XmlParser.NodeRecord("instance_material", ProcessInstanceMaterial),
			new XmlParser.NodeRecord("instance_geometry", ProcessInstanceGeometry),
			new XmlParser.NodeRecord("bind_material", ProcessGenericNode<ColladaMaterialBinding>),
			
			
		};

		private static void ProcessCommonNodeAttributes(XmlReader r, Node node)
		{
			ColladaNode n = node as ColladaNode;
			n.Name = r.GetAttribute("name");
			n.Id = r.GetAttribute("id");
		}

		private static Node ProcessGenericNode<T>(XmlReader r, Node parent) where T : Node, new()
		{
			T obj = new T();
			ProcessCommonNodeAttributes(r, obj);
			return obj;
		}

		private static Node ProcessAsset(XmlReader r, Node parent)
		{
			ColladaAsset obj = new ColladaAsset();
			obj.Name = r.GetAttribute("name");
			obj.Id = r.GetAttribute("id");
			while (r.Read())
			{
				if (r.NodeType == XmlNodeType.Element)
				{
					switch (r.Name.ToLower())
					{
						case "created": obj.Created = r.ReadElementContentAsString(); break;
						case "modified": obj.Modified = r.ReadElementContentAsString(); break;
						case "up_axis": obj.UpAxis = r.ReadElementContentAsString(); break;
						default: r.Skip(); break;
					}
				}
			}
			return obj;
		}

		private static Node ProcessContributor(XmlReader r, Node parent)
		{
			ColladaContributor obj = new ColladaContributor();
			ProcessCommonNodeAttributes(r, obj);
			while (r.Read())
			{
				if (r.NodeType == XmlNodeType.Element)
				{
					switch (r.Name.ToLower())
					{
						case "author": obj.Author = r.ReadElementContentAsString(); break;
						case "authoring_tool": obj.AuthoringTool = r.ReadElementContentAsString(); break;
						case "comments": obj.Comments = r.ReadElementContentAsString(); break;
						case "copyright": obj.Copyright = r.ReadElementContentAsString(); break;
						default: r.Skip(); break;
					}
				}
			}
			return obj;
		}

		private static Node ProcessUnit(XmlReader r, Node parent)
		{
			ColladaUnit obj = new ColladaUnit();
			ProcessCommonNodeAttributes(r, obj);
			obj.Meter = float.Parse(r.GetAttribute("meter"));
			return obj;
		}

		private static Node ProcessPerspective(XmlReader r, Node parent)
		{
			ColladaPerspective obj = new ColladaPerspective();
			ProcessCommonNodeAttributes(r, obj);
			while (r.Read())
			{
				if (r.NodeType == XmlNodeType.Element)
				{
					switch (r.Name.ToLower())
					{
						case "yfov": obj.YFov = float.Parse(r.ReadElementContentAsString()); break;
						case "aspect_ratio": obj.AspectRatio = float.Parse(r.ReadElementContentAsString()); break;
						case "znear": obj.ZNear = float.Parse(r.ReadElementContentAsString()); break;
						case "zfar": obj.ZFar = float.Parse(r.ReadElementContentAsString()); break;
						default: r.Skip(); break;
					}
				}
			}
			return obj;
		}

		private static Node ProcessPointLight(XmlReader r, Node parent)
		{
			ColladaPointLight obj = new ColladaPointLight();
			ProcessCommonNodeAttributes(r, obj);
			while (r.Read())
			{
				if (r.NodeType == XmlNodeType.Element)
				{
					switch (r.Name.ToLower())
					{
						case "color": break;
						case "constant_attenuation": break;
						case "linear_attenuation": break;
						case "quadratic_attenuation": break;
						default: r.Skip(); break;
					}
				}
			}
			return obj;
		}

		private static Node ProcessInstance<T>(XmlReader r, Node parent)
		{
			ColladaInstance<T> obj = new ColladaInstance<T>();
			ProcessCommonNodeAttributes(r, obj);
			obj.Url = r.GetAttribute("url");
			return obj;
		}

		private static Node ProcessPhong(XmlReader r, Node parent)
		{
			ColladaPhong obj = new ColladaPhong();
			ProcessCommonNodeAttributes(r, obj);
			while (r.Read())
			{
				if (r.NodeType == XmlNodeType.Element)
				{
					switch (r.Name.ToLower())
					{
						case "emission": break;
						case "ambient": break;
						case "diffuse": break;
						case "specular": break;
						case "shininess": break;
						case "reflective": break;
						case "reflectivity": break;
						case "transparent": break;
						case "transparency": break;
						case "index_of_refraction": break;
						default: r.Skip(); break;
					}
				}
			}
			return obj;
		}

		private static Node ProcessArray<T>(XmlReader r, Node parent)
		{
			ColladaArray<T> obj = new ColladaArray<T>();
			ProcessCommonNodeAttributes(r, obj);
			obj.Elements = new T[int.Parse(r.GetAttribute("count"))];
			String[] elements = r.ReadElementContentAsString().Split(' ');
			MethodInfo m = typeof(T).GetMethod("Parse", new Type[] { typeof(string) });
			for (int i = 0; i < elements.Length; ++i)
				obj.Elements[i] = (T)m.Invoke(null, new object[] { elements[i] });
			return obj;
		}

		private static Node ProcessAccessor(XmlReader r, Node parent)
		{
			ColladaAccessor obj = new ColladaAccessor();
			ProcessCommonNodeAttributes(r, obj);
			obj.Count = int.Parse(r.GetAttribute("count"));
			obj.Offset = int.Parse(r.GetAttribute("offset"));
			obj.Source = r.GetAttribute("source");
			obj.Stride = int.Parse(r.GetAttribute("stride"));
			return obj;
		}

		private static Node ProcessParam(XmlReader r, Node parent)
		{
			ColladaParam obj = new ColladaParam();
			ProcessCommonNodeAttributes(r, obj);
			obj.Type = r.GetAttribute("type");
			return obj;
		}

		private static Node ProcessInput(XmlReader r, Node parent)
		{
			ColladaInput obj = new ColladaInput();
			ProcessCommonNodeAttributes(r, obj);
			obj.Semantic = r.GetAttribute("semantic");
			obj.Source = r.GetAttribute("source");
			return obj;
		}

		private static Node ProcessPolylist(XmlReader r, Node parent)
		{
			ColladaPolylist obj = new ColladaPolylist();
			ProcessCommonNodeAttributes(r, obj);
			obj.Count = int.Parse(r.GetAttribute("count"));
			obj.Material = r.GetAttribute("material");
			while (r.Read())
			{
				if (r.NodeType == XmlNodeType.Element)
				{
					switch (r.Name.ToLower())
					{
						case "vcount":
							{
								String[] vcounts = r.ReadElementContentAsString().Split(' ');
								obj.VCount = new int[vcounts.Length];
								for (int i = 0; i < vcounts.Length; ++i)
									obj.VCount[i] = int.Parse(vcounts[i]);
								break;
							}
						case "p": 
							{
								String[] p = r.ReadElementContentAsString().Split(' ');
								obj.P = new int[p.Length];
								for (int i = 0; i < p.Length; ++i)
									obj.P[i] = int.Parse(p[i]);
								break;
							}
						default: r.Skip(); break;
					}
				}
			}
			return obj;
		}

		private static Node ProcessVisualSceneNode(XmlReader r, Node parent)
		{
			ColladaVisualSceneNode obj = new ColladaVisualSceneNode();
			ProcessCommonNodeAttributes(r, obj);
			while (r.Read())
			{
				if (r.NodeType == XmlNodeType.Element)
				{
					switch (r.Name.ToLower())
					{
						case "translate":
							{
								String[] bits = r.ReadElementContentAsString().Split(' ');
								Debug.Assert(bits.Length == 3);
								obj.Translate[0] = float.Parse(bits[0]);
								obj.Translate[1] = float.Parse(bits[1]);
								obj.Translate[2] = float.Parse(bits[2]);
								break;
							}
						case "rotate":
							{
								int row = -1;
								String rowName = r.GetAttribute("sid");
								Debug.Assert(rowName != null);
								switch (rowName.ToLower())
								{
									case "rotatex": row = 0; break;
									case "rotatey": row = 1; break;
									case "rotatez": row = 2; break;
								}
								Debug.Assert(row != -1);
								String[] bits = r.ReadElementContentAsString().Split(' ');
								Debug.Assert(bits.Length == 4);
								obj.Rotate[row, 0] = float.Parse(bits[0]);
								obj.Rotate[row, 1] = float.Parse(bits[1]);
								obj.Rotate[row, 2] = float.Parse(bits[2]);
								obj.Rotate[row, 3] = float.Parse(bits[3]);

								break;
							}
					}
				}
			}
			return obj;
		}

		private static Node ProcessInstanceMaterial(XmlReader r, Node parent)
		{
			ColladaInstanceMaterial obj = new ColladaInstanceMaterial();
			ProcessCommonNodeAttributes(r, obj);
			obj.Symbol = r.GetAttribute("symbol");
			obj.Target = r.GetAttribute("target");
			return obj;
		}

		private static Node ProcessInstanceGeometry(XmlReader r, Node parent)
		{
			ColladaInstanceGeometry obj = new ColladaInstanceGeometry();
			ProcessCommonNodeAttributes(r, obj);
			obj.Url = r.GetAttribute("url");
			return obj;
		}

		private static Node ProcessTechnique(XmlReader r, Node parent)
		{
			ColladaTechnique obj = new ColladaTechnique();
			ProcessCommonNodeAttributes(r, obj);
			obj.sId = r.GetAttribute("sid");
			obj.Profile = r.GetAttribute("profile");
			return obj;
		}

		#endregion
	}
}
